package fr.utbm.to52.android_bibtex.main;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import net.barbierdereuille.android.AndroBib.BibTexParser;
import net.barbierdereuille.android.AndroBib.BibTexParser.BibTexEntry;
import net.barbierdereuille.android.AndroBib.BibTexParser.BibTexItem;
import net.barbierdereuille.android.AndroBib.BibTexParser.BibTexPreamble;
import net.barbierdereuille.android.AndroBib.BibTexParser.BibTexString;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ListView;
import fr.utbm.to52.android_bibtex.R;
import fr.utbm.to52.android_bibtex.controller.EntryListAdapter;
import fr.utbm.to52.android_bibtex.controller.ListViewItem;
import fr.utbm.to52.android_bibtex.controller.MainController;
import fr.utbm.to52.android_bibtex.model.entry.Entry;
import fr.utbm.to52.android_bibtex.model.entry.EntryList;
import fr.utbm.to52.android_bibtex.model.entry.Field;
import fr.utbm.to52.android_bibtex.model.spec.EntryDefinition;
import fr.utbm.to52.android_bibtex.model.spec.EntryDefinition.FieldSpecificationCategory;
import fr.utbm.to52.android_bibtex.model.spec.EntryDefinitions;
import fr.utbm.to52.android_bibtex.model.spec.FieldSpecification;
import fr.utbm.to52.android_bibtex.model.spec.FieldSpecifications;
import fr.utbm.to52.android_bibtex.model.spec.Month;
import fr.utbm.to52.android_bibtex.model.spec.ValueType;

public class MainActivity extends Activity {

	private static final String TAG = "MainActivity";

	private ListView listEntries;
	private MainController controller;

	private final String filename = "/data/data/fr.utbm.to52.android_bibtex/biblio.bib";
	private File file;

	public static List<BibTexItem> bibtexItems;
	public static boolean entryListChanged = false;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		this.controller = new MainController(this);
		this.listEntries = (ListView) findViewById(R.id.listViewEntries);
		this.listEntries.setOnItemClickListener(this.controller);

		this.file = new File(this.filename);
		Log.d(TAG, "Before test");
		if (EntryDefinitions.getInstance().getDefinitions().isEmpty()) {
			Log.d(TAG, "Begin in test");
			// TODO we tried to put the following in a new Thread/AsyncTask (and display a ProgressDialog),
			//      but it was much MUCH SLOWER! more than x10!
			try {
				initModel();
			} catch (Exception e) {
				AlertDialog.Builder dialog = new AlertDialog.Builder(this);
				dialog.setTitle("Exception in initModel()");
				dialog.setMessage(e.getMessage());
				dialog.setNeutralButton("Close", new OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						finish();
					}
				});
				dialog.create().show();
			}
		}
		Log.d(TAG, "After test");

	}

	@Override
	protected void onResume() {
		super.onResume();
		invalidateOptionsMenu();
		setListView();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.menu, menu);
		/* 0: add entry, 1: save, 2: settings */
		menu.getItem(1).setEnabled(false);
		menu.getItem(2).setEnabled(false);
		// TODO Upload file menu
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		return this.controller.itemSelectedAction(item, this.file);
	}

	@Override
	public boolean onPrepareOptionsMenu (Menu menu) {
		if (entryListChanged) {
			menu.getItem(1).setEnabled(true);
		}
		return true;
	}

	public MainController getController() {
		return this.controller;
	}

	private void initModel() throws Exception {
		initModelSpec();
		initModelEntry();
	}

	private void initModelSpec() throws Exception {
		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();

		final int[] fileIds = {
				R.raw.predefined_types,
				R.raw.user_defined_types,
		};
		for (int fileId : fileIds) {
			InputStream inputStream = getResources().openRawResource(fileId);
			Document document = documentBuilder.parse(inputStream);

			Element rootElement = document.getDocumentElement();

			// fields
			{
				NodeList nodes = rootElement.getElementsByTagName("field");
				for (int i = 0; i < nodes.getLength(); ++i) {
					Node node = nodes.item(i);
					NamedNodeMap nodeAttributes = node.getAttributes();

					String nodeAttrId = nodeAttributes.getNamedItem("id").getNodeValue();
					String nodeAttrType = nodeAttributes.getNamedItem("type").getNodeValue();

					String name = nodeAttrId;
					ValueType type = ValueType.valueOf(nodeAttrType);

					FieldSpecification fs = new FieldSpecification(name, type);

					if (!FieldSpecifications.getInstance().addSpecification(fs)) {
						throw new Exception("Failed to register FieldSpecification with name "+fs.getName());
					}
				}
			}

			// entries
			{
				NodeList nodes = rootElement.getElementsByTagName("entry");
				for (int i = 0; i < nodes.getLength(); ++i) {
					Element node = (Element) nodes.item(i);
					NamedNodeMap nodeAttributes = node.getAttributes();

					String nodeAttrId = nodeAttributes.getNamedItem("id").getNodeValue();

					String name = nodeAttrId;

					EntryDefinition ed = new EntryDefinition(name);

					initModelSpecEntryDefinitionFieldSpecifications(ed, node.getElementsByTagName("required-field"), FieldSpecificationCategory.REQUIRED);
					initModelSpecEntryDefinitionFieldSpecifications(ed, node.getElementsByTagName("recommended-field"), FieldSpecificationCategory.RECOMMENDED);

					final String[] forcedRequiredFields = {
							"title",
							"author",
							"year",
					};
					for (String fsName : forcedRequiredFields) {
						FieldSpecification fs = FieldSpecifications.getInstance().getSpecification(fsName);
						assert fs!=null;
						FieldSpecificationCategory category = ed.getCategoryOf(fs);
						if (!FieldSpecificationCategory.REQUIRED.equals(category)) {
							if (category != null) {
								boolean fsRemoved = ed.removeFieldSpecificationOfCategory(category, fs);
								assert fsRemoved;
							}
							boolean fsAdded = ed.addFieldSpecificationOfCategory(FieldSpecificationCategory.REQUIRED, fs);
							assert fsAdded;
						}
					}

					if (!EntryDefinitions.getInstance().addDefinition(ed)) {
						throw new Exception("Failed to register EntryDefinition with name "+ed.getName());
					}
				}
			}
		}
	}

	private void initModelSpecEntryDefinitionFieldSpecifications(EntryDefinition ed, NodeList nodes, FieldSpecificationCategory category) throws Exception {
		List<FieldSpecification> fieldSpecifications = new ArrayList<FieldSpecification>();

		for (int i = 0; i < nodes.getLength(); ++i) {
			Node node = nodes.item(i);
			NamedNodeMap nodeAttributes = node.getAttributes();

			String nodeAttrFieldId = nodeAttributes.getNamedItem("field-id").getNodeValue();

			String fsName = nodeAttrFieldId;

			FieldSpecification fs = FieldSpecifications.getInstance().getSpecification(fsName);
			if (fs==null) {
				throw new Exception("Inexistent FieldSpecification with name "+fsName);
			}

			fieldSpecifications.add(fs);
		}

		int addedCount = ed.setFieldSpecificationsOfCategory(category, fieldSpecifications);
		if (addedCount < fieldSpecifications.size()) {
			String fsName = fieldSpecifications.get(addedCount).getName();
			throw new Exception("Failed to add FieldSpecification with name "+fsName+
					" to category "+category.name()+" of EntryDefinition with name "+ed.getName());
		}
	}

	private void initModelEntry() throws Exception {
		if (!this.file.exists()) {
			return;
		}

		Log.d(TAG, "creating BibTexParser");
		BibTexParser parser = new BibTexParser(this.file);
		Log.d(TAG, "begin of parsing");
		bibtexItems = parser.parse();
		Log.d(TAG, "end of parsing");

		Log.d(TAG, "looping over bibtexItems");
		for (BibTexItem item : bibtexItems) {
			switch(item.type()) {
			case ENTRY: {
				BibTexEntry bibtexEntry = (BibTexEntry) item;
				Log.d(TAG, "processing entry "+bibtexEntry.type+" "+bibtexEntry.key);

				EntryDefinition definition = EntryDefinitions.getInstance().getDefinition(bibtexEntry.type);
				if (definition==null) {
					throw new Exception("Inexistent EntryDefinition with name "+bibtexEntry.type);
				}
				Entry entry = new Entry(definition, bibtexEntry.key);
				for (Pair<String,String> pair : bibtexEntry.fields) {
					Log.d(TAG, "processing field "+pair.first);
					FieldSpecification specification = FieldSpecifications.getInstance().getSpecification(pair.first);
					if (specification==null) {
						throw new Exception("Inexistent FieldSpecification with name "+pair.first);
					}
					Field field = new Field(specification);
					Object fieldValue = null;
					switch (specification.getType()) {
					case MONTH:
						fieldValue = Month.fromShortName(pair.second);
						break;
					default:
						fieldValue = pair.second;
					}
					field.setValue(fieldValue);
					if (!entry.addField(field)) {
						throw new Exception("Failed add Field with specification "+field.getSpecification().getName()+
								" to entry "+entry.getKey());
					}
				}

				// Add missing required fields (to avoid a NullPointerException)
				Set<FieldSpecification> requiredFsSet = entry.getDefinition().getFieldSpecificationsOfCategory(FieldSpecificationCategory.REQUIRED);
				for (FieldSpecification fs : requiredFsSet) {
					if (!entry.getFields().containsKey(fs)) {
						Field field = new Field(fs);
						// TODO value?
						entry.addField(field);
					}
				}

				if (!EntryList.getInstance().addEntry(entry)) {
					throw new Exception("Failed add Entry "+entry.getKey());
				}
				break;
			}
			case STRING: {
				BibTexString bibtexString = (BibTexString) item;
				// TODO
				break;
			}
			case PREAMBLE: {
				BibTexPreamble bibtexPreamble = (BibTexPreamble) item;
				// TODO
				break;
			}
			}
		}
	}

	public void setListView() {
		//		FieldSpecification titleFs = FieldSpecifications.getInstance().getSpecification("title");
		//		List<Map<String, String>> data = new ArrayList<Map<String, String>>();
		//		for (Entry e : EntryList.getInstance().getEntryList().values()) {
		//			Map<String, String> details = new HashMap<String, String>(2);
		//			details.put("title", e.getFields().get(titleFs).getValue().toString());
		//			details.put("type", e.getDefinition().getName());
		//			data.add(details);
		//		}
		//		SimpleAdapter adapter = new SimpleAdapter(this, data, android.R.layout.simple_list_item_2,
		//				new String[] {"title", "type"}, new int[] {android.R.id.text1, android.R.id.text2});
		//		this.listEntries.setAdapter(adapter);
		//		this.listEntries.setOnItemLongClickListener(this.controller);
		//		this.listEntries.setOnItemClickListener(this.controller);
		ArrayList<ListViewItem> entries = getItems();
		Collections.sort(entries, new Comparator<ListViewItem>() {
			@Override
			public int compare(ListViewItem lvi1, ListViewItem lvi2) {
				String year1 = lvi1.getYear();
				String year2 = lvi2.getYear();
				int yComp = year2.compareTo(year1);

				if (yComp != 0) {
					return yComp;
				} else {
					String title1 = lvi1.getTitle().toLowerCase(Locale.getDefault());
					String title2 = lvi2.getTitle().toLowerCase(Locale.getDefault());
					return title1.compareTo(title2);
				}
			}
		});
		this.listEntries.setAdapter(new EntryListAdapter(this, entries));
		this.listEntries.setOnItemClickListener(this.controller);
	}

	public ArrayList<ListViewItem> getItems() {
		ArrayList<ListViewItem> items = new ArrayList<ListViewItem>();

		FieldSpecification titleFs = FieldSpecifications.getInstance().getSpecification("title");
		FieldSpecification authorFs = FieldSpecifications.getInstance().getSpecification("author");
		FieldSpecification yearFs = FieldSpecifications.getInstance().getSpecification("year");
		for (Entry e : EntryList.getInstance().getEntryList().values()) {
			ListViewItem lvi = new ListViewItem();
			lvi.setTitle(e.getFields().get(titleFs).stringValue());
			lvi.setAuthor(e.getFields().get(authorFs).stringValue());
			lvi.setYear(e.getFields().get(yearFs).stringValue());
			lvi.setType(e.getDefinition().getName());
			lvi.setKey(e.getKey());
			items.add(lvi);
		}
		return items;
	}
}
